package com.poly.flowable.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.poly.common.core.domain.R;
import com.poly.common.core.tools.utils.Func;
import com.poly.common.core.utils.AssertUtil;
import com.poly.common.security.utils.SecurityUtils;
import com.poly.flowable.constant.ProcessConstants;
import com.poly.flowable.domain.SysForm;
import com.poly.flowable.enums.ApproveStatus;
import com.poly.flowable.mapper.FlowDeployMapper;
import com.poly.flowable.pojo.dto.FlowProcDefDTO;
import com.poly.flowable.pojo.model.NodeModel;
import com.poly.flowable.pojo.model.NodeUserModel;
import com.poly.flowable.pojo.vo.HandleModeVO;
import com.poly.flowable.factory.FlowServiceFactory;
import com.poly.flowable.service.IFlowDefinitionService;
import com.poly.flowable.service.ISysFormService;
import com.poly.system.domain.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 流程定义
 *
 * @author Yang Huijing
 * @date 2021-04-03
 */
@Service
@Slf4j
public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFlowDefinitionService {

    @Autowired
    private ISysFormService sysFormService;

    @Resource
    private FlowDeployMapper flowDeployMapper;

    private static final String BPMN_FILE_SUFFIX = ".bpmn";

    @Override
    public boolean exist(String processDefinitionKey) {
        ProcessDefinitionQuery processDefinitionQuery
                = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey);
        long count = processDefinitionQuery.count();
        return count > 0;
    }


    /**
     * 流程定义列表
     *
     * @param pageNum  当前页码
     * @param pageSize 每页条数
     * @return 流程定义分页列表数据
     */
    @Override
    public Page<FlowProcDefDTO> list(String name, String category, Integer pageNum, Integer pageSize) {
        Page<FlowProcDefDTO> page = new Page<>();
//        // 流程定义列表数据查询
//        final ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//        if (StringUtils.isNotEmpty(name)) {
//            processDefinitionQuery.processDefinitionNameLike(name);
//        }
////        processDefinitionQuery.orderByProcessDefinitionKey().asc();
//        page.setTotal(processDefinitionQuery.count());
//        List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage(pageSize * (pageNum - 1), pageSize);
//
//        List<FlowProcDefDto> dataList = new ArrayList<>();
//        for (ProcessDefinition processDefinition : processDefinitionList) {
//            String deploymentId = processDefinition.getDeploymentId();
//            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
//            FlowProcDefDto reProcDef = new FlowProcDefDto();
//            BeanUtils.copyProperties(processDefinition, reProcDef);
//            SysForm sysForm = formService.selectSysFormByDeployId(deploymentId);
//            if (Objects.nonNull(sysForm)) {
//                reProcDef.setFormName(sysForm.getFormName());
//                reProcDef.setFormId(sysForm.getFormId());
//            }
//            // 流程定义时间
//            reProcDef.setDeploymentTime(deployment.getDeploymentTime());
//            dataList.add(reProcDef);
//        }
        PageHelper.startPage(pageNum, pageSize);
        final List<FlowProcDefDTO> dataList = flowDeployMapper.selectDeployList(name, category);
        // 加载挂表单
        for (FlowProcDefDTO procDef : dataList) {
            SysForm sysForm = sysFormService.selectSysFormByDeployId(Long.parseLong(procDef.getDeploymentId()));
            if (Objects.nonNull(sysForm)) {
                procDef.setFormName(sysForm.getFormName());
                procDef.setFormId(sysForm.getFormId());
            }
        }
        page.setTotal(new PageInfo(dataList).getTotal());
        page.setRecords(dataList);
        return page;
    }


    /**
     * 导入流程文件
     * <p>
     * 当每个key的流程第一次部署时，指定版本为1。对其后所有使用相同key的流程定义，
     * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义
     *
     * @param name
     * @param category
     * @param in
     */
    @Override
    public void importFile(String name, String category, InputStream in) {
        Deployment deploy = repositoryService.createDeployment().addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category).deploy();
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
        repositoryService.setProcessDefinitionCategory(definition.getId(), category);

    }

    /**
     * 读取xml
     *
     * @param deployId
     * @return
     */
    @Override
    public R readXml(String deployId) throws IOException {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
        String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
        return R.ok(result);
    }

    /**
     * 读取xml
     *
     * @param deployId
     * @return
     */
    @Override
    public InputStream readImage(String deployId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        //获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        //输出为图片
        return diagramGenerator.generateDiagram(
                bpmnModel,
                "png",
                Collections.emptyList(),
                Collections.emptyList(),
                "宋体",
                "宋体",
                "宋体",
                null,
                1.0,
                false);

    }

    /**
     * 根据流程定义ID启动流程实例
     *
     * @param procDefId 流程模板ID
     * @param variables 流程变量
     * @return
     */
    @Override
    public String startProcessInstanceById(String procDefId, String businessKey, Map<String, Object> variables) {
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
                    .latestVersion().singleResult();
            AssertUtil.isFalse(Objects.nonNull(processDefinition) && processDefinition.isSuspended(), "流程已被挂起,请先激活流程");
            // 设置流程发起人Id到流程中
            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
            identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
            variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId());
            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, businessKey, variables);
            // 流程发起时 跳过发起人节点
//            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
//            identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
//            variables.put(ProcessConstants.PROCESS_INITIATOR, "");
//            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
//            // 给第一步申请人节点设置任务执行人和意见
//            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
//            if (Objects.nonNull(task)) {
//                taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请");
////                taskService.setAssignee(task.getId(), sysUser.getUserId().toString());
//                taskService.complete(task.getId(), variables);
//            }
            return processInstance.getId();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 激活或挂起流程定义
     *
     * @param state    状态
     * @param deployId 流程部署ID
     */
    @Override
    public void updateState(Integer state, String deployId) {
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 激活
        if (state == 1) {
            repositoryService.activateProcessDefinitionById(procDef.getId(), true, null);
        }
        // 挂起
        if (state == 2) {
            repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null);
        }
    }


    /**
     * 删除流程定义
     *
     * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值
     */
    @Override
    public void delete(String deployId) {
        // true 允许级联删除 ,不设置会导致数据库外键关联异常
        repositoryService.deleteDeployment(deployId, true);
    }

    /**
     * 获取指定类型流程定义的最新发布版本
     */
    @Override
    public FlowProcDefDTO getProcDefByCategory(String category) {
        FlowProcDefDTO flowProcDefDto = flowDeployMapper.selectDeployByCategory(category);
        Assert.isTrue(!Func.isNull(flowProcDefDto), "没有激活的流程可用");
        SysForm sysForm = sysFormService.selectSysFormByDeployId(Long.parseLong(flowProcDefDto.getDeploymentId()));
        if (Objects.nonNull(sysForm)) {
            flowProcDefDto.setFormName(sysForm.getFormName());
            flowProcDefDto.setFormId(sysForm.getFormId());
        }
        return flowProcDefDto;
    }


    /**
     * 流向
     *
     * @param task
     * @return
     */
    private synchronized List<String> getFlowDirection(Task task) {
        List<String> flowDirectionList = new ArrayList<>();
        // 取出任务定义的KEY
        String taskDefinitionKey = task.getTaskDefinitionKey();
        // 找出流程定义的ID
        String processDefinitionId = task.getProcessDefinitionId();
        // 根据流程定义ID找到流程定义对象
        ProcessDefinition processDefinition = this.repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId).singleResult();
        // 取出流程定义的KEY
        String key = processDefinition.getKey();
        // 通过流程定义ID找到BPM的对象
        BpmnModel bpmnModel = this.repositoryService.getBpmnModel(processDefinitionId);
        // 根据流程定义KEY找到从bpmnModel里面找出流程定义
        Process process = bpmnModel.getProcessById(key);
        // 取出流程定义对象里面的所有节点
        Collection<FlowElement> flowElements = process.getFlowElements();
        for (FlowElement flowElement : flowElements) {
            // 找出里面的UserTask节点
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                if (userTask.getId().equals(taskDefinitionKey)) {
                    // 取出出口连线信息
                    List<SequenceFlow> outgoingFlows = userTask.getOutgoingFlows();
                    for (SequenceFlow sequenceFlow : outgoingFlows) {
                        // 取出名字 【页面按钮上的名字】
                        // 如果名字为空 那么不添加
                        if (Func.isNotBlank(sequenceFlow.getName())) {
                            flowDirectionList.addAll(Arrays.asList(sequenceFlow.getName().split("\\,")));
                        }

                    }
                    break;
                }
            }
        }
        return flowDirectionList;
    }

    /**
     * 通过用户和业务获取任务审批流向
     *
     * @param userId
     * @param businessId
     * @param processInstanceId
     * @return
     */
    @Override
    public HandleModeVO getTaskFlowDirection(String userId, String businessId, String processInstanceId) {
        try {
            List<String> ocList = new ArrayList<>();
            List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
            if (null != taskList && taskList.size() > 0) {
//                HistoricTaskInstance historicTask = getWithdrawHistoricTask(taskList.get(0), userId);
//                if (Func.isNotEmpty(historicTask)) {
//                    ocList.add("撤回");
//                }
                Task task = taskList.stream().filter(p -> userId.equals(p.getAssignee())).findFirst().orElse(null);
                if (Func.isNotEmpty(task)) {
                    ProcessInstance processInstance = this.runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
                    if (processInstance != null && businessId.equals(processInstance.getBusinessKey())) {
                        ocList.addAll(getFlowDirection(task));
                        ocList.sort(new Comparator<String>() {
                            @Override
                            public int compare(String o1, String o2) {
                                return o1.compareTo(o2);
                            }
                        });
//                        if (Func.isEmpty(historicTask)) {
//                            ocList.add("转办");
//                        }
                    }
                }
                if (Func.isNotEmpty(ocList)) {
                    if (Func.isNotEmpty(task)) {
                        return new HandleModeVO(task.getId(), ocList);
                    } else {
                        return new HandleModeVO(taskList.get(0).getId(), ocList);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HandleModeVO(null, null);
    }

    public List<HistoricTaskInstance> getHistoricTaskInstanceList(String processInstanceId) {
        List<HistoricTaskInstance> taskInstanceList = this.historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId).list();
        if (null != taskInstanceList) {
            taskInstanceList.sort((o1, o2) -> o2.getTime().compareTo(o1.getTime()));
        }
        return taskInstanceList;
    }

    public List<Comment> getTaskCommentList(String processInstanceId) {
        List<Comment> commentList = this.taskService.getProcessInstanceComments(processInstanceId);
        if (null != commentList) {
            commentList.sort((o1, o2) -> o2.getTime().compareTo(o1.getTime()));
        }
        return commentList;
    }

    private void setAdopt(List<NodeUserModel> auList, List<Task> taskList, List<Comment> commentList,
                          List<HistoricTaskInstance> historicTaskInstanceList, NodeModel ap) {
        for (NodeUserModel au : auList) {
            int adopt = ApproveStatus.not_started.getValue();
            if (null != taskList) {
                if (taskList.stream().filter(p ->
                        ap.getId().equals(p.getTaskDefinitionKey())
                                && String.valueOf(au.getUserId()).equals(p.getAssignee())).count() > 0L) {
                    adopt = ApproveStatus.approval.getValue();
                }
            }
            if (null != historicTaskInstanceList) {
                List<HistoricTaskInstance> htiList = historicTaskInstanceList.stream().filter(p ->
                        ap.getId().equals(p.getTaskDefinitionKey())
                                && String.valueOf(au.getUserId()).equals(p.getAssignee())
                ).collect(Collectors.toList());
                if (!htiList.isEmpty()) {
                    Comment comment = commentList.stream().filter(p ->
                            htiList.get(0).getId().equals(p.getTaskId())
                    ).findFirst().orElse(null);
                    if (Func.isNotEmpty(comment)) {
                        if (adopt == 0) {
                            if ("同意,通过".contains(comment.getType())) {
                                adopt = ApproveStatus.passed.getValue();
                            } else if ("驳回,拒绝,不同意,不通过".contains(comment.getType())) {
                                adopt = ApproveStatus.rejected.getValue();
                            }
                        }
                        au.setCommentId(comment.getId());
                        au.setComment("【" + comment.getType() + "】" + comment.getFullMessage());
                        au.setApprovalTime(comment.getTime());
                    }
                }
            }
            au.setAdopt(adopt);
        }
        if (auList.stream().anyMatch(p -> ApproveStatus.rejected.getValue().equals(p.getAdopt()))) {
            ap.setAdopt(ApproveStatus.rejected.getValue());
        } else if (auList.stream().anyMatch(p -> ApproveStatus.passed.getValue().equals(p.getAdopt()))) {
            ap.setAdopt(ApproveStatus.passed.getValue());
        } else if (auList.stream().anyMatch(p -> ApproveStatus.approval.getValue().equals(p.getAdopt()))) {
            ap.setAdopt(ApproveStatus.approval.getValue());
        } else if (ap.getAdopt() == null || ap.getAdopt() == 0) {
            ap.setAdopt(ApproveStatus.not_started.getValue());
        }
    }

    public String getAssigneeUserId(Map<String, Object> variables, UserTask userTask) {
        if (variables == null) {
            variables = new HashMap<>();
        }
        String assignee = null;
        if (Func.isNotEmpty(userTask.getLoopCharacteristics()) && Func.isNotEmpty(userTask.getLoopCharacteristics().getInputDataItem())) {
            assignee = userTask.getLoopCharacteristics().getInputDataItem();
        } else if (StringUtils.isNotBlank(userTask.getAssignee()) && Func.isNotEmpty(userTask.getAssignee())) {
            assignee = userTask.getAssignee();
        }
        String userIds = assignee;
        if (StringUtils.isNotBlank(assignee) && Pattern.compile("\\#\\{[^\\}]+\\}|\\$\\{[^\\}]+\\}").matcher(assignee).find()) {
            String[] s = getAssigneeKey(assignee);
            if (s == null) {
                return "";
            }
            Object value = null;
            if (s.length == 1 && variables.containsKey(s[0])) {
                value = variables.get(s[0]);
            } else if (s.length == 2 && variables.containsKey(s[0])) {
                if (variables.get(s[0]) instanceof HashMap) {
                    Map<String, Object> var2 = (Map<String, Object>) variables.get(s[0]);
                    if (var2 != null && var2.containsKey(s[1])) {
                        value = var2.get(s[1]);
                    }
                } else if (variables.get(s[0]) instanceof JSONObject) {
                    JSONObject var2 = (JSONObject) variables.get(s[0]);
                    if (var2 != null && var2.containsKey(s[1])) {
                        value = var2.get(s[1]);
                    }
                }
            }
            if (value instanceof JSONArray) {
                userIds = ArrayUtil.join(JSONArray.parseArray(((JSONArray) value).toJSONString(), String.class).toArray(), ",");
            } else if (value instanceof ArrayList) {
                userIds = ArrayUtil.join(((ArrayList) value).toArray(), ",");
            } else {
                userIds = String.valueOf(value);
            }
        }
        return userIds;
    }

    public String getAssignee(UserTask userTask) {
        String assignee = null;
        if (Func.isNotEmpty(userTask.getLoopCharacteristics())) {
            MultiInstanceLoopCharacteristics milc = userTask.getLoopCharacteristics();
            if (Func.isNotEmpty(milc.getInputDataItem())) {
                assignee = getAssigneeKeyStr(milc.getInputDataItem());
            }
        } else {
            assignee = getAssigneeKeyStr(userTask.getAssignee());
        }
        return assignee;
    }

    public String getAssigneeKeyStr(String assignee) {
        String[] assigneeKey = getAssigneeKey(assignee);
        if (assigneeKey == null) {
            return "";
        }
        return assigneeKey.length == 1 ? assigneeKey[0] : assigneeKey[1];
    }

    public String[] getAssigneeKey(String assignee) {
        if (Func.isBlank(assignee)) {
            return null;
        }
        assignee = assignee.replaceAll("\\#\\{", "")
                .replaceAll("\\$\\{", "")
                .replaceAll("\\}", "").trim();
        return assignee.split("\\.");

    }

    @Override
    public Map<String, Object> getDeployByFormId(Long formId) {
        return flowDeployMapper.getDeployByFormId(formId);
    }
}
